typedef enum {
    PSH,    // push
    ADD,
    POP,
    SET,
    HLT     // end
} InstructionSet;

typedef enum {
    IP, SP,
    REGISTERS_COUNT
} Registers;

// 2021-05-03 stack pointer, stored in register SP
#define sp regs[SP]
// 2021-05-03 instruction pointer, stored in register IP
#define ip regs[IP]
#define STACK_SIZE 1024

class VM {
private:
    int data[STACK_SIZE] = {0};
    int regs[REGISTERS_COUNT] = {0};
    // 2021-05-03 instruction set
    const int* program = nullptr;

public:
    // vm status
    bool running = false;
    VM() = default;
    explicit VM(const int* program);
    ~VM();
    // 2021-05-03 get next instruction
    int fetch();
    // 2021-05-03 evaluate instruction
    void eval(int ins);
};

VM::VM(const int* program) {
    // 2021-05-03 stack pointer
    sp = -1;
    // 2021-05-03 instruction pointer
    ip = -1;
    this->program = program;
    this->running = true;
}

VM::~VM() {
    // 2021-05-03 stack pointer
    sp = -1;
    // 2021-05-03 instruction pointer
    ip = -1;
    this->running = false;
}

int VM::fetch() {
    ip++;
    return program[ip];
}

void VM::eval(int ins) {
    switch (ins) {
        case HLT:
            this->running = false;
            break;
        case PSH:
            ip++;
            sp++;
            if (sp >= STACK_SIZE - 1) {
                fprintf(stderr, "Error: stack over flow, stack size: %d.\n", STACK_SIZE);
                this->running = false;
                break;
            }
            this->data[sp] = program[ip];
            break;
        case ADD: {
            if (sp == -1) {
                fprintf(stderr, "Error: parameter count 0, cannot ADD.");
                this->running = false;
                break;
            }
            int right = this->data[sp];
            sp--;
            if (sp == -1) {
                fprintf(stderr, "Error: parameter count 1, cannot ADD.");
                this->running = false;
                break;
            }
            int left = this->data[sp];
            sp--;
            int result = left + right;
            sp++;
            this->data[sp] = result;
            break;
        }
        case POP: {
            int top = this->data[sp];
            printf("POP %d\n", top);
            sp--;
            break;
        }
        case SET: {
            ip++;
            int regI = ip;
            ip++;
            regs[program[regI]] = program[ip];
            break;
        }
        default:
            fprintf(stderr, "No matched instruction %d\n", ins);
            this->running = false;
            break;
    }
}